---
title: 스크립트 및 이벤트 처리
description: >-
  기본 브라우저 JavaScript API를 사용하여 Astro 컴포넌트에 클라이언트 측 상호 작용을 추가하는 방법.
i18nReady: true
---

import ReadMore from '~/components/ReadMore.astro'

컴포넌트 템플릿의 `<script>` 태그를 사용하여 브라우저로 JavaScript를 전송하고 Astro 컴포넌트에 기능을 추가할 수 있습니다.

스크립트는 React, Svelte, Vue와 같은 [UI 프레임워크](/ko/guides/framework-components/) 없이 이벤트를 처리하거나 콘텐츠를 동적으로 업데이트하는 등 사이트에 상호 작용을 추가할 수 있습니다. 따라서 프레임워크 JavaScript의 오버헤드를 피할 수 있으며 모든 기능을 갖춘 웹사이트나 애플리케이션을 만들기 위해 다른 프레임워크를 학습할 필요가 없습니다.

## 클라이언트 측 스크립트

스크립트를 사용하여 이벤트 리스너를 추가하고, 분석 데이터를 전송하고, 애니메이션을 재생하는 등 JavaScript가 웹에서 수행할 수 있는 모든 작업을 수행할 수 있습니다.

Astro는 번들링, TypeScript 등을 통해 HTML 표준 `<script>` 태그를 개선합니다. 자세한 내용은 [Astro가 스크립트를 처리하는 방법](#스크립트-처리하기)을 참조하세요.

```astro title="src/components/ConfettiButton.astro"
<button data-confetti-button>Celebrate!</button>

<script>
  // npm 패키지에서 가져옵니다.
  import confetti from 'canvas-confetti';

  // 페이지에서 컴포넌트 DOM을 찾습니다.
  const buttons = document.querySelectorAll('[data-confetti-button]');

  // 버튼을 클릭하면 confetti를 실행하는 이벤트 리스너를 추가합니다.
  buttons.forEach((button) => {
    button.addEventListener('click', () => confetti());
  });
</script>
```

<ReadMore>[스크립트가 처리되지 않는 시기](#처리되지-않은-스크립트)를 확인하여 스크립트 동작 문제를 해결하거나 의도적으로 이 처리를 거부하는 방법을 알아보세요.</ReadMore>

## 스크립트 처리하기

기본적으로 `<script>` 태그는 Astro에서 처리됩니다.

기본적으로 Astro는 `src` 외에 다른 속성이 없는 `<script>` 태그를 다음과 같은 방식으로 처리합니다.

- **TypeScript 지원:** 모든 스크립트는 기본적으로 TypeScript입니다.
- **번들 가져오기:** 함께 번들링된 로컬 파일 또는 npm 모듈을 가져옵니다.
- **타입 모듈:** 처리된 스크립트는 자동으로 [`type="module"`](https://developer.mozilla.org/ko/docs/Web/JavaScript/Guide/Modules)이 됩니다.
- **중복 제거:** `<script>`가 포함된 컴포넌트가 한 페이지에서 여러 번 사용되면 스크립트는 오직 한 번만 포함됩니다.
- **자동 인라인 처리:** 스크립트가 충분히 작은 경우, Astro는 요청 횟수를 줄이기 위해 스크립트를 HTML에 직접 인라인 처리합니다.

```astro title="src/components/Example.astro"
<script>
  // 처리, 번들링 및 타입스크립트가 지원됩니다!
  // 로컬 스크립트를 가져올 수 있으며, npm 패키지에서 가져올 수도 있습니다.
</script>
```

### 처리되지 않은 스크립트

`<script>` 태그에 `src` 이외의 속성이 있는 경우 Astro는 이를 처리하지 않습니다.

[`is:inline`](/ko/reference/directives-reference/#isinline) 지시어를 추가하여 스크립트 처리를 의도적으로 거부할 수 있습니다.

```astro title="src/components/InlineScript.astro" "is:inline"
<script is:inline>
  // 작성된 그대로 HTML로 렌더링됩니다!
  // 변환되지 않음: TypeScript 처리 및 Astro의 가져오기 경로 해석이 적용되지 않습니다.
  // 컴포넌트에서 사용하는 경우, 이 코드는 각 인스턴스마다 복제됩니다.
</script>
```

### 페이지에 JavaScript 파일 포함

스크립트를 별도의 `.js`/`.ts` 파일로 작성하거나 다른 서버의 외부 스크립트를 참조해야 할 수도 있습니다. `<script>` 태그의 `src` 속성에서 이를 참조하여 수행할 수 있습니다.

#### 로컬 스크립트 가져오기

**사용 시기:** 스크립트가 `src/` 디렉터리에 있을 때

Astro는 [스크립트 처리 규칙](#스크립트-처리하기)에 따라 이러한 스크립트를 처리합니다.

```astro title="src/components/LocalScripts.astro"
<!-- `src/scripts/local.js`에 있는 스크립트의 상대 경로 -->
<script src="../scripts/local.js"></script>

<!-- 로컬 TypeScript 파일에도 작동합니다. -->
<script src="./script-with-types.ts"></script>
```

#### 외부 스크립트 로드

**사용 시기:** JavaScript 파일이 `public/` 또는 CDN에 있는 경우.

프로젝트의 `src/` 폴더 외부에 있는 스크립트를 로드하려면 `is:inline` 지시어를 포함하세요. 이 접근 방식은 위에서 설명한 대로 스크립트를 가져올 때 Astro에서 제공하는 JavaScript 처리, 번들링 및 최적화를 건너뜁니다.

```astro title="src/components/ExternalScripts.astro" "is:inline"
<!-- `public/my-script.js`에 있는 스크립트의 절대 경로 -->
<script is:inline src="/my-script.js"></script>

<!-- 원격 서버의 스크립트에 대한 전체 URL -->
<script is:inline src="https://my-analytics.com/script.js"></script>
```

## 일반적인 스크립트 패턴

### `onclick` 및 기타 이벤트 처리

일부 UI 프레임워크는 이벤트 처리를 위해 `onClick={...}` (React/Preact) 또는 `@click="..."` (Vue)과 같은 사용자 정의 구문을 사용합니다. Astro는 표준 HTML을 더 밀접하게 따르며 이벤트에 사용자 정의 구문을 사용하지 않습니다.

대신 `<script>` 태그에서 [`addEventListener`](https://developer.mozilla.org/ko/docs/Web/API/EventTarget/addEventListener)를 사용하여 사용자 상호작용을 처리할 수 있습니다.

```astro title="src/components/AlertButton.astro"
<button class="alert">Click me!</button>

<script>
  // 페이지에서 `alert` 클래스가 포함된 모든 버튼을 찾습니다.
  const buttons = document.querySelectorAll('button.alert');

  // 각 버튼의 클릭을 처리합니다.
  buttons.forEach((button) => {
    button.addEventListener('click', () => {
      alert('Button was clicked!');
    });
  });
</script>
```

한 페이지에 여러 `<AlertButton />` 컴포넌트가 있는 경우 Astro는 스크립트를 여러 번 실행하지 않습니다. 스크립트는 번들로 제공되며 페이지당 한 번만 포함됩니다. `querySelectorAll`을 사용하면 이 스크립트가 페이지에 있는 `alert` 클래스를 가진 모든 버튼에 이벤트 리스너를 연결합니다.

### 사용자 정의 요소가 있는 웹 컴포넌트

웹 컴포넌트 표준을 사용하여 사용자 정의 동작으로 자신만의 HTML 요소를 만들 수 있습니다. `.astro` 컴포넌트에 [사용자 정의 요소](https://developer.mozilla.org/ko/docs/Web/API/Web_components/Using_custom_elements)를 정의하면 UI 프레임워크 라이브러리 없이도 대화형 컴포넌트를 구축할 수 있습니다.

다음 예시에서는 하트 버튼을 클릭한 횟수를 추적하고 `<span>`의 값을 최신 횟수로 업데이트하는 새 `<astro-heart>` HTML 요소를 정의합니다.

```astro title="src/components/AstroHeart.astro"
<!-- 사용자 정의 요소 "astro-heart"에 컴포넌트 요소를 래핑합니다. -->
<astro-heart>
  <button aria-label="Heart">💜</button> × <span>0</span>
</astro-heart>

<script>
  // 새로운 유형의 HTML 요소에 대한 동작을 정의합니다.
  class AstroHeart extends HTMLElement {
    connectedCallback() {
      let count = 0;

      const heartButton = this.querySelector('button');
      const countSpan = this.querySelector('span');

      // 버튼을 클릭할 때마다 개수를 업데이트합니다.
			heartButton.addEventListener('click', () => {
        count++;
        countSpan.textContent = count.toString();
      });
		}
  }

  // <astro-heart> 요소에 AstroHeart 클래스를 사용하도록 브라우저에 지시합니다.
  customElements.define('astro-heart', AstroHeart);
</script>
```

여기에서 사용자 정의 요소를 사용하면 두 가지 이점이 있습니다.

1. `document.querySelector()`를 사용하여 전체 페이지를 검색하는 대신 현재 사용자 정의 요소 인스턴스에서만 검색하는 `this.querySelector()`를 사용할 수 있습니다. 이렇게 하면 한 번에 하나의 컴포넌트 인스턴스의 하위 항목으로만 작업하는 것이 더 쉬워집니다.

2. `<script>`는 한 번만 실행되지만 브라우저는 페이지에서 `<astro-heart>`를 찾을 때마다 사용자 정의 요소의 `connectedCallback()` 메서드를 실행합니다. 즉, 한 페이지에서 이 컴포넌트를 여러 번 사용하려는 경우에도 한 번에 하나의 컴포넌트에 대한 코드를 안전하게 작성할 수 있습니다.

<ReadMore>[web.dev의 재사용 가능한 웹 컴포넌트 안내서](https://web.dev/custom-elements-v1/)와 [MDN의 사용자 정의 요소 소개](https://developer.mozilla.org/ko/docs/Web/API/Web_components/Using_custom_elements)에서 사용자 정의 요소에 대해 자세히 알아볼 수 있습니다.</ReadMore>

### 프런트매터 변수를 스크립트에 전달

Astro 컴포넌트의 [프런트매터](/ko/basics/astro-components/#컴포넌트-스크립트) (`---` 펜스 내부)에 있는 코드는 서버에서 실행되며, 브라우저에서는 사용할 수 없습니다.

서버 측 변수를 클라이언트 측 스크립트에 전달하려면 변수를 HTML 요소의 [`data-*` 속성](https://developer.mozilla.org/ko/docs/Web/HTML/How_to/Use_data_attributes)에 저장하세요. 그러면 스크립트에서 `dataset` 속성을 사용하여 이 값에 액세스할 수 있습니다.

이 예시 컴포넌트에서 `message` prop은 `data-message` 속성에 저장되므로 사용자 정의 요소는 `this.dataset.message`를 읽고 브라우저에서 prop 값을 가져올 수 있습니다.

```astro title="src/components/AstroGreet.astro" {2} /data-message={.+}/ "this.dataset.message"
---
const { message = 'Welcome, world!' } = Astro.props;
---

<!-- message prop을 data 속성으로 저장합니다. -->
<astro-greet data-message={message}>
  <button>Say hi!</button>
</astro-greet>

<script>
  class AstroGreet extends HTMLElement {
    connectedCallback() {
      // data 속성에서 메시지를 읽습니다.
      const message = this.dataset.message;
      const button = this.querySelector('button');
      button.addEventListener('click', () => {
        alert(message);
      });
		}
  }

  customElements.define('astro-greet', AstroGreet);
</script>
```

이제 컴포넌트를 여러 번 사용할 수 있으며 각 컴포넌트마다 다른 메시지가 표시됩니다.

```astro title="src/pages/example.astro"
---
import AstroGreet from '../components/AstroGreet.astro';
---

<!-- 기본 메시지인 "Welcome, world!"를 사용하세요. -->
<AstroGreet />

<!-- props로 전달된 사용자 정의 메시지를 사용하세요. -->
<AstroGreet message="Lovely day to build components!" />
<AstroGreet message="Glad you made it! 👋" />
```

:::tip[알고 계셨나요?]
이것은 실제로 React와 같은 UI 프레임워크를 사용하여 작성된 컴포넌트에 props를 전달할 때 Astro가 뒤에서 수행하는 작업입니다! `client:*` 지시어가 있는 컴포넌트의 경우 Astro는 HTML 출력에 서버 측 props를 저장하는 `props` 속성이 있는 `<astro-island>` 사용자 정의 요소를 생성합니다.
:::

### 스크립트와 UI 프레임워크 결합하기

UI 프레임워크에 의해 렌더링되는 요소들은 `<script>` 태그가 실행될 때 아직 사용 가능하지 않을 수 있습니다. 스크립트에서 [UI 프레임워크 컴포넌트](/ko/guides/framework-components/)도 처리해야 하는 경우 사용자 정의 요소를 사용하는 것이 좋습니다.
